package com.insight.common.util;

import lombok.extern.slf4j.Slf4j;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * T020: Jaccard 相似度计算工具类
 * 用于检测表重命名和结构相似度
 * <p>
 * Jaccard相似度 = |A ∩ B| / |A ∪ B|
 * 取值范围: [0, 1]，值越大表示越相似
 * </p>
 */
@Slf4j
public class SimilarityUtil {

    /**
     * 默认相似度阈值（>80%认为是重命名）
     */
    public static final double DEFAULT_SIMILARITY_THRESHOLD = 0.80;

    /**
     * 计算两个集合的 Jaccard 相似度
     *
     * @param set1 集合1
     * @param set2 集合2
     * @param <T>  元素类型
     * @return 相似度值 [0, 1]
     */
    public static <T> double jaccardSimilarity(Collection<T> set1, Collection<T> set2) {
        if (set1 == null || set2 == null || set1.isEmpty() && set2.isEmpty()) {
            return 0.0;
        }

        Set<T> union = new HashSet<>(set1);
        union.addAll(set2);

        Set<T> intersection = new HashSet<>(set1);
        intersection.retainAll(set2);

        if (union.isEmpty()) {
            return 0.0;
        }

        return (double) intersection.size() / union.size();
    }

    /**
     * 计算两个字符串集合的相似度
     *
     * @param collection1 字符串集合1
     * @param collection2 字符串集合2
     * @return 相似度值 [0, 1]
     */
    public static double calculateSimilarity(Collection<String> collection1, Collection<String> collection2) {
        return jaccardSimilarity(collection1, collection2);
    }

    /**
     * 判断两个表结构是否相似（基于列名）
     * 相似度超过阈值则认为可能是重命名
     *
     * @param columnNames1 表1的列名列表
     * @param columnNames2 表2的列名列表
     * @param threshold    相似度阈值 (0-1)
     * @return true 如果相似度超过阈值
     */
    public static boolean isSimilar(Collection<String> columnNames1,
                                     Collection<String> columnNames2,
                                     double threshold) {
        double similarity = calculateSimilarity(columnNames1, columnNames2);
        return similarity >= threshold;
    }

    /**
     * 判断两个表结构是否相似（使用默认阈值80%）
     *
     * @param columnNames1 表1的列名列表
     * @param columnNames2 表2的列名列表
     * @return true 如果相似度≥80%
     */
    public static boolean isSimilar(Collection<String> columnNames1, Collection<String> columnNames2) {
        return isSimilar(columnNames1, columnNames2, DEFAULT_SIMILARITY_THRESHOLD);
    }

    /**
     * 检测表重命名
     * 如果两个表的列结构高度相似（≥80%），则认为可能是重命名
     *
     * @param oldTable 旧表信息（包含列名列表）
     * @param newTable 新表信息（包含列名列表）
     * @return true 如果可能是重命名
     */
    public static boolean isPossibleRename(Map<String, Object> oldTable, Map<String, Object> newTable) {
        @SuppressWarnings("unchecked")
        List<String> oldColumns = (List<String>) oldTable.get("columnNames");
        @SuppressWarnings("unchecked")
        List<String> newColumns = (List<String>) newTable.get("columnNames");

        if (oldColumns == null || newColumns == null) {
            return false;
        }

        return isSimilar(oldColumns, newColumns);
    }

    /**
     * 计算余弦相似度（基于向量）
     * 用于比较两个表的列类型分布
     *
     * @param vector1 向量1（例如：各数据类型的列数量）
     * @param vector2 向量2
     * @return 相似度值 [0, 1]
     */
    public static double cosineSimilarity(Map<String, Integer> vector1, Map<String, Integer> vector2) {
        if (vector1 == null || vector2 == null || vector1.isEmpty() || vector2.isEmpty()) {
            return 0.0;
        }

        // 计算点积
        double dotProduct = 0.0;
        for (String key : vector1.keySet()) {
            if (vector2.containsKey(key)) {
                dotProduct += vector1.get(key) * vector2.get(key);
            }
        }

        // 计算模长
        double magnitude1 = Math.sqrt(vector1.values().stream()
                .mapToDouble(v -> v * v)
                .sum());
        double magnitude2 = Math.sqrt(vector2.values().stream()
                .mapToDouble(v -> v * v)
                .sum());

        if (magnitude1 == 0 || magnitude2 == 0) {
            return 0.0;
        }

        return dotProduct / (magnitude1 * magnitude2);
    }

    /**
     * 计算编辑距离（Levenshtein距离）
     * 用于比较表名相似度
     *
     * @param str1 字符串1
     * @param str2 字符串2
     * @return 编辑距离（最小编辑操作数）
     */
    public static int levenshteinDistance(String str1, String str2) {
        if (str1 == null || str2 == null) {
            return Integer.MAX_VALUE;
        }

        int m = str1.length();
        int n = str2.length();

        int[][] dp = new int[m + 1][n + 1];

        for (int i = 0; i <= m; i++) {
            dp[i][0] = i;
        }
        for (int j = 0; j <= n; j++) {
            dp[0][j] = j;
        }

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                }
            }
        }

        return dp[m][n];
    }

    /**
     * 计算归一化编辑距离（0-1）
     * 归一化距离 = 1 - (编辑距离 / 最大长度)
     *
     * @param str1 字符串1
     * @param str2 字符串2
     * @return 相似度值 [0, 1]
     */
    public static double normalizedLevenshteinSimilarity(String str1, String str2) {
        if (str1 == null || str2 == null) {
            return 0.0;
        }

        if (str1.equals(str2)) {
            return 1.0;
        }

        int distance = levenshteinDistance(str1, str2);
        int maxLength = Math.max(str1.length(), str2.length());

        if (maxLength == 0) {
            return 1.0;
        }

        return 1.0 - ((double) distance / maxLength);
    }

    /**
     * 综合相似度评分（结合多种算法）
     * 权重：Jaccard 60% + 编辑距离 40%
     *
     * @param tableName1   表名1
     * @param columnNames1 表1列名集合
     * @param tableName2   表名2
     * @param columnNames2 表2列名集合
     * @return 综合相似度值 [0, 1]
     */
    public static double comprehensiveSimilarity(String tableName1, Collection<String> columnNames1,
                                                  String tableName2, Collection<String> columnNames2) {
        // Jaccard 相似度（列名结构）
        double jaccardScore = jaccardSimilarity(columnNames1, columnNames2);

        // 编辑距离相似度（表名）
        double nameScore = normalizedLevenshteinSimilarity(
                tableName1.toLowerCase(),
                tableName2.toLowerCase()
        );

        // 加权平均
        return jaccardScore * 0.6 + nameScore * 0.4;
    }

    /**
     * 私有构造函数，防止实例化
     */
    private SimilarityUtil() {
        throw new UnsupportedOperationException("Utility class cannot be instantiated");
    }
}
